#include "db.h"
#include <sys/uio.h>

DB* db_open(const char *pathname, int oflag, int mode)
{
    DB *db;
    int i, len;
    char asciiptr[PTR_SZ + 1],
        hash[(NHASH_DEF +1) * PTR_SZ + 2];
    struct stat statbuff;
    len = strlen(pathname);
    if((db=_db_alloc(len))==NULL)
    {
        err_dump("_db_alloc error for DB");
    }
    db->oflag = oflag;
    strcpy(db->name, pathname);
    strcat(db->name, ".idx");
    if((db->idxfd = open(db->name, oflag, mode))<0)
    {
        _db_free(db);
        return (NULL);
    }
    strcpy(db->name + len, ".dat");
    if((db->datfd = open(db->name, oflag, mode))<0)
    {
        _db_free(db);
        return (NULL);
    }
    if((oflag & (O_CREAT| O_TRUNC)) == (O_CREAT| O_TRUNC))
    {
        if(writew_lock(db->idxfd,0, SEEK_SET, 0) < 0)
        {
            err_dump("writew_lock error");
        }
        if(fstat(db->idxfd, &statbuff) < 0)
        {
            err_sys("fstat error");
        }
        if(statbuff.st_size == 0)
        {
            sprintf(asciiptr, "%*d", PTR_SZ, 0);
            hash[0] = 0;
            for(i = 0;i<(NHASH_DEF +1);i++)
            {
                strcat(hash, asciiptr);
            }
            strcat(hash, "\n");
            i = strlen(hash);
            if(write(db->idxfd, hash, i) !=i)
            {
                err_dump("write error initializing index file");
            }
        }
        if(un_lock(db->idxfd,0,SEEK_SET,0)<0)
        {
            err_dump("unlock error");
        }
    }
    db->nhash = NHASH_DEF;
    db->hashoff = HASH_OFF;
    db_rewind(db);
    return (db);
}



DB* _db_alloc(int namelen)
{
    DB *db;
    if((db =calloc(1,sizeof(DB)))==NULL)
    {
        err_dump("calloc error for DB");
    }
    db->idxfd = db->datfd = -1;
    if((db->name = malloc(namelen + 5))==NULL)
    {
        err_dump("malloc error for name");
    }
    if((db->idxbuf = malloc(IDXLEN_MAX + 2)) == NULL)
    {
        err_dump("malloc error for index buffer");
    }
    if((db->datbuf = malloc(DATLEN_MAX + 2)) == NULL)
    {
        err_dump("malloc error for data buffer");
    }
    return (db);
}

int _db_free(DB *db)
{
    if(db->idxfd >= 0 && close(db->idxfd)<0)
    {
        err_dump("index close error");
    }
    if(db->datfd >= 0 && close(db->datfd)<0)
    {
        err_dump("data close error");
    }
    db->idxfd = db->datfd = -1;
    if(db->idxbuf != NULL)
    {
        free(db->idxbuf);
    }
    if(db->datbuf != NULL)
    {
        free(db->datbuf);
    }
    if(db->name != NULL)
    {
        free(db->name);
    }
    free(db);
    return (0);
}

void db_close(DB *db)
{
    _db_free(db);
}

char* db_fetch(DB *db, const char *key)
{
    char *ptr;
    if(_db_find(db, key,0) < 0)
    {
        ptr=NULL;
        db->cnt_fetcherr++;
    }
    else
    {
        ptr = _db_readdat(db);
        db->cnt_fetchok++;
    }

    if(un_lock(db->idxfd, db->chainoff, SEEK_SET,1) < 0)
    {
        err_dump("un_lock error");
    }
    return(ptr);
}


int _db_find(DB *db, const char *key, int writelock)
{
    off_t offset, nextoffset;
    db->chainoff = (_db_hash(db, key) * PTR_SZ) + db->hashoff;
    db->ptroff = db->chainoff;

    if(writelock)
    {
        if(writew_lock(db->idxfd, db->chainoff, SEEK_SET,1) <0)
        {
            err_dump("writew_lock error");
        }
    }
    else
    {
        if(readw_lock(db->idxfd, db->chainoff, SEEK_SET,1)<0)
        {
            err_dump("readw_lock error");
        }
    }
    offset = _db_readptr(db, db->ptroff);

    while(offset != 0 )
    {
        nextoffset = _db_readidx(db, offset);
        if(strcmp(db->idxbuf, key) == 0)
        {
            break;
        }
        db->ptroff = offset;
        offset = nextoffset;
    }

    if(offset == 0)
    {
        return (-1); 
    }
    return (0);
}

hash_t _db_hash(DB *db, const char *key)
{
    hash_t hval;
    const char *ptr;
    char c;
    int i;
    hval =0;
    for(ptr = key,i=1;c=*ptr++;i++)
    {
        hval += c * i;
    }
    return hval% db->nhash;
}

off_t _db_readptr(DB *db, off_t offset)
{
    char asciiptr[PTR_SZ + 1];
    if(lseek(db->idxfd, offset, SEEK_SET) ==-1)
    {
        err_dump("lseek error to ptr field");
    }
    if(read(db->idxfd, asciiptr, PTR_SZ)!=PTR_SZ)
    {
        err_dump("read error of ptr field");
    }
    asciiptr[PTR_SZ] = 0;
    return(atol(asciiptr));
}

off_t _db_readidx (DB *db, off_t offset)
{
    int i;
    char *ptr1, *ptr2;
    char asciiptr[PTR_SZ +1], asciilen[IDXLEN_SZ+1];
    struct iovec iov[2];
    if((db->idxoff =lseek(db->idxfd, offset, offset ==0? SEEK_CUR: SEEK_SET)) == -1)
    {
        err_dump("lseek error");
    }
    iov[0].iov_base = asciiptr;
    iov[0].iov_len = PTR_SZ;
    iov[1].iov_base = asciilen;
    iov[1].iov_len = IDXLEN_SZ;
    
    if((i=readv(db->idxfd, &iov[0], 2))!=PTR_SZ + IDXLEN_SZ)
    {
        if(i == 0 && offset == 0)
        {
            return (-1);
        }
        err_dump("readv error of index record");
    }
    asciiptr[PTR_SZ] = 0;
    db->ptrval = atol(asciiptr);
    asciilen[IDXLEN_SZ] = 0;
    if((db->idxlen = atoi(asciilen))<IDXLEN_MIN ||
            db->idxlen > IDXLEN_MAX )
    {
        err_dump("invalid length");
    }

    if((i=read(db->idxfd, db->idxbuf, db->idxlen))!=db->idxlen)
    {
        err_dump("read error of index record");
    }
    if(db->idxbuf[db->idxlen-1] != '\n')
    {
        err_dump("read error of index record");
    }
    db->idxbuf[db->idxlen-1] = 0;

    if((ptr1=strchr(db->idxbuf, SEP))==NULL)
    {
        err_dump("missing first separtor");
    }
    *ptr1++ = 0;
    if(strchr(ptr2, SEP) !=NULL)
    {
        err_dump("too many separators");
    }
    if((db->datoff = atol(ptr1))<0)
    {
        err_dump("starting offset");
    }
    if((db->datlen = atol(ptr2))<=0 || db->datlen> DATLEN_MAX)
    {
        err_dump("invalid length");
    }
    return (db->ptrval);
}

char * _db_readdat(DB *db)
{
    if(lseek(db->datfd, db->datoff, SEEK_SET) == -1)
    {
        err_dump("lseek error");
    }
    if(read(db->datfd, db->datbuf, db->datlen) != db->datlen)
    {
        err_dump("read error");
    }
    if(db->datbuf[db->datlen -1] !='\n')
    {
        err_dump("missing newline");
    }
    db->datbuf[db->datlen-1] = 0;
    return (db->datbuf);
}

int db_delete(DB *db, const char *key)
{
    int rc;
    if(_db_find(db, key, 1) ==0)
    {
        rc = _db_dodelete(db);
        db->cnt_delok ++ ;
    }
    else
    {
        rc = -1;
        db->cnt_delerr ++;
    }
    if(un_lock(db->idxfd, db->chainoff, SEEK_SET, 1) < 0)
    {
        err_dump("un_lock error");
    }
    return (rc);
}

int _db_dodelete(DB *db)
{
    int i;
    char *ptr;
    off_t freeptr, saveptr;
    for(ptr= db->datbuf, i=0; i<db->datlen-1;i++)
    {
        *ptr++ =' ';
    }
    *ptr =0;
    ptr =db->idxbuf;
    while(*ptr)
    {
        *ptr++ = ' ';
    }
    if(writew_lock(db->idxfd, FREE_OFF, SEEK_SET,1)<0)
    {
        err_dump("writew_lock error");
    }
    _db_writedat(db,db->datbuf,db->datoff,SEEK_SET);
    freeptr = _db_readptr(db, FREE_OFF);
    saveptr = db->ptrval;
    _db_writeidx(db, db->idxbuf, db->idxoff, SEEK_SET, freeptr);
    _db_writeptr(db, FREE_OFF, db->idxoff);
    _db_writeptr(db,db->ptroff, saveptr);
    if(un_lock(db->idxfd,FREE_OFF, SEEK_SET, 1)<0)
    {
        err_dump("un_lock error");
    }
    return (0);
}

void _db_writedat(DB *db, const char *data, off_t offset, int whence)
{
    struct iovec iov[2];
    static char newline = '\n';
    if(whence == SEEK_END)
    {
        if(writew_lock(db->datfd, 0, SEEK_SET, 0) < 0)
        {
            err_dump("writew_lock errror");
        }
    }
    db->datlen = strlen(data);
    iov[0].iov_base= (char*)data;
    iov[0].iov_len=db->datlen-1;
    iov[1].iov_base=&newline;
    iov[1].iov_len=1;
    if(writev(db->datfd, &iov[0],2)!= db->datlen)
    {
        if(un_lock(db->datfd, 0, SEEK_SET, 0) < 0)
        {
            err_dump("un_lock error");
        }
    }
}

void _db_writeidx(DB *db, const char *key, off_t offset, int whence, off_t ptrval)
{
    struct iovec iov[2];
    char asciiptrlen[PTR_SZ + IDXLEN_SZ + 1];
    int len;
    if((db->ptrval = ptrval) <0 || ptrval> PTR_MAX)
    {
        err_quit("invalid ptr:%d", ptrval);
    }
    sprintf(db->idxbuf, "%s%c%d%c%d\n", key, SEP, db->datoff, SEP, db->datlen);
    if((len= strlen(db->idxbuf))< IDXLEN_MIN || len > IDXLEN_MAX)
    {
        err_dump("invalid length");
    }
    sprintf(asciiptrlen, "%*d%*d", PTR_SZ, ptrval,IDXLEN_SZ, len);
    if(whence == SEEK_END)
    {
        if(writew_lock(db->idxfd,((db->nhash+1)*PTR_SZ)+1, SEEK_SET, 0) < 0)
        {
            err_dump("wriew_lock error");
        }
    }
    if((db->idxoff == lseek(db->idxfd, offset, whence))== -1)
    {
        err_dump("lseek error");
    }
    iov[0].iov_base = asciiptrlen;
    iov[0].iov_len = PTR_SZ+ IDXLEN_SZ;
    iov[1].iov_base = db->idxbuf;
    iov[1].iov_len = len;
    if(writev(db->idxfd, &iov[0],2)!=PTR_SZ+IDXLEN_SZ+len)
    {
        err_dump("writev error of index record");
    }

    if(whence == SEEK_END)
    {
        if(un_lock(db->idxfd, ((db->nhash+1)*PTR_SZ)+1, SEEK_SET,0)< 0)
        {
            err_dump("un_lock error");
        }
    }
}


void _db_writeptr(DB *db, off_t offset, off_t ptrval)
{
    char asciiptr[PTR_SZ +1];
    if( ptrval < 0 || ptrval > PTR_MAX)
    {
        err_quit("invalid ptr: %d", ptrval);
    }
    sprintf(asciiptr,"%*d", PTR_SZ, ptrval);
    if(lseek(db->idxfd, offset, SEEK_SET) == -1)
    {
        err_dump("lseek error to ptr field");
    }
    if(write(db->idxfd, asciiptr, PTR_SZ)!= PTR_SZ)
    {
        err_dump("write error of ptr field");
    }   
}

int db_store(DB *db, const char *key, const char *data, int flag)
{
    int rc, keylen, datlen;
    off_t ptrval;
    keylen = strlen(key);
    datlen = strlen(data) +1;
    if(datlen < DATLEN_MIN || datlen > DATLEN_MAX)
    {
        err_dump("invalid data length");
    }
    if(_db_find(db, key, 1)<0)
    {
        if(flag & DB_REPLACE)
        {
            rc=-1;
            db->cnt_storerr ++;
            goto doreturn;
        }
        ptrval = _db_readptr(db, db->chainoff);
        if(_db_findfree(db, keylen, datlen) < 0)
        {
            _db_writedat(db, data,0, SEEK_END);
            _db_writeidx(db, key,0, SEEK_END, ptrval);
            _db_writeptr(db, db->chainoff, db->idxoff);
            db->cnt_stor1 ++;
        }
        else
        {
            _db_writedat(db, data, db->datoff, SEEK_SET);
            _db_writeidx(db, key, db->idxoff, SEEK_SET, ptrval);
            _db_writeptr(db,db->chainoff, db->idxoff);
            db->cnt_stor2 ++;
        }
    }
    else
    {
        if(flag & DB_INSERT)
        {
            rc =1;
            db->cnt_storerr++;
            goto doreturn;
        }
        if(datlen != db->datlen)
        {
            _db_dodelete(db);
            ptrval = _db_readptr(db, db->chainoff);
            _db_writedat(db,data,0,SEEK_END);
            _db_writeidx(db,key, 0, SEEK_END, ptrval);
            _db_writeptr(db, db->chainoff, db->idxoff);
            db->cnt_stor3 ++;
        }
        else
        {
            _db_writedat(db, data, db->datoff, SEEK_SET);
            db->cnt_stor4++;
        }
    }
    rc = 0;
doreturn:
    if(un_lock(db->idxfd, db->chainoff, SEEK_SET,1)<0)
    {
        err_dump("un_lock error");
    }
    return (rc);

}

int _db_findfree(DB *db, int keylen, int datlen)
{
    int rc;
    off_t offset, nextoffset, saveoffset;
    if(writew_lock(db->idxfd, FREE_OFF, SEEK_SET, 1)< 0)
    {
        err_dump("writew_lock error");
    }

    saveoffset = FREE_OFF;
    offset = _db_readptr(db, saveoffset);
    while(offset!=0)
    {
        nextoffset = _db_readidx(db, offset);
        if(strlen(db->idxbuf) == keylen && db->datlen == datlen)
        {
            break;
        }
        saveoffset = offset;
        offset = nextoffset;
    }
    if(offset == 0)
    {
        rc = -1;
    }
    else
    {
        _db_writeptr(db, saveoffset, db->ptrval);
        rc = 0;
    }
    if(un_lock(db->idxfd, FREE_OFF, SEEK_SET, 1) < 0)
    {
        err_dump("un_lock error");
    }

    return (rc);
}

void db_rewind(DB *db)
{
    off_t offset;
    offset = (db->nhash + 1) * PTR_SZ;
    if( (db->idxoff = lseek(db->idxfd, offset +1, SEEK_SET))==-1)
    {
        err_dump("lseek error");
    }
}

char *db_nextrec(DB *db, char *key)
{
    char c, *ptr;
    if(readw_lock(db->idxfd, FREE_OFF, SEEK_SET,1)<0)
    {
        err_dump("readw_locak error");
    }
    do 
    {
        if(_db_readidx(db,0)<0)
        {
            ptr = NULL;
            goto doreturn;
        }
        ptr = db->idxbuf;
        while( (c=*ptr++)!=0 && c == ' ')
        {
            ;
        }
    } while (c == 0);
    if(key !=NULL)
    {
        strcpy(key, db-> idxbuf);
    }
    ptr = _db_readdat(db);
    db-> cnt_nextrec++;
doreturn:
    if(un_lock(db->idxfd, FREE_OFF, SEEK_SET,1)<0)
    {
        err_dump("un_lock error");
    } 
    return (ptr);
}